﻿/* ================================================================================
<copyright file="TimeTaken.cs" company="The Framework Factory, LLC" lastWriteTime="12/05/2012 23:23:47 PM">
  Copyright (c) 2012 The Framework Factory, LLC.  All rights reserved.
</copyright>
=================================================================================== */
using System;
using System.Collections.Generic;
using System.Diagnostics;

using log4net.Core;
using log4net.Util;

namespace FWF.Logging.Statistics
{
    /// <summary>
    /// A <see cref="IDisposable"/> component used to record time to a specified <see cref="IFWFLog"/>.
    /// </summary>
    /// <remarks>
    /// An example implementation:
    /// <code lang="C#">
    /// using( new TimeTaken() )
    /// {
    ///    // Do something
    /// }
    /// </code>
    /// </remarks>
    public class TimeTaken : IDisposable
    {
        private string _category;
        private string _name;
        private string _instance;
        private IFWFLog _log;
        private readonly Stopwatch _stopWatch = new Stopwatch();
        private readonly List<TimedMessage> _messages = new List<TimedMessage>();
        private bool _disposed;
        private bool _isClosed;

        /// <summary>
        /// 
        /// </summary>
        private class TimedMessage
        {
            public TimeSpan Elapsed;
            public string Message;
        }

        #region Constructors / Finalizer

        /// <summary>
        /// Initializes a new instance of the <see cref="TimeTaken"/> class.
        /// </summary>
        private TimeTaken()
        {
        }

        /// <summary>
        /// Releases unmanaged resources and performs other cleanup operations before the
        /// <see cref="TimeTaken"/> is reclaimed by garbage collection.
        /// </summary>
        ~TimeTaken()
        {
            Dispose(false);
        }

        #endregion

        /// <summary>
        /// Starts recording immediately when the object is created.
        /// </summary>
        /// <param name="log">The log.</param>
        /// <param name="category">The category.</param>
        /// <param name="name">The name.</param>
        /// <returns></returns>
        public static TimeTaken Start(IFWFLog log, string category, string name)
        {
            return Start(log, category, name, string.Empty);
        }

        /// <summary>
        /// Starts the specified log.
        /// </summary>
        /// <param name="log">The log.</param>
        /// <param name="category">The category.</param>
        /// <param name="name">The name.</param>
        /// <param name="instance">The instance.</param>
        /// <returns></returns>
        public static TimeTaken Start(IFWFLog log, string category, string name, string instance)
        {
            var tt = new TimeTaken();
            tt._category = category;
            tt._name = name;
            tt._instance = instance;
            tt._log = log;

            //
            tt._log.TraceFormat(
                "{0}:{1} {2}: {3}",
                tt._category,
                tt._name,
                "00:00:00.0000000",
                "Start"
                );

            //
            tt._stopWatch.Start();

            return tt;
        }

        #region IDisposable Members

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);

            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Releases all resources used by the current <see cref="T:System.Timers.Timer"/>.
        /// </summary>
        /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
        private void Dispose(bool disposing)
        {
            if (!_disposed)
            {
                _disposed = true;

                if (IsRunning)
                {
                    Close();
                }
            }
        }

        #endregion

        /// <summary>
        /// Gets the category.
        /// </summary>
        /// <value>The category.</value>
        public string Category
        {
            get
            {
                return _category;
            }
        }

        /// <summary>
        /// Gets the name.
        /// </summary>
        /// <value>The name.</value>
        public string Name
        {
            get
            {
                return _name;
            }
        }

        /// <summary>
        /// Gets the instance.
        /// </summary>
        /// <value>The instance.</value>
        public string Instance
        {
            get
            {
                return _instance;
            }
        }

        /// <summary>
        /// Gets a value indicating whether this instance is running.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance is running; otherwise, <c>false</c>.
        /// </value>
        public bool IsRunning
        {
            get
            {
                return !_isClosed;
            }
        }

        /// <summary>
        /// Adds the message.
        /// </summary>
        /// <param name="message">The message.</param>
        public void AddMessage(string message)
        {
            var tm = new TimedMessage();
            tm.Elapsed = _stopWatch.Elapsed;
            tm.Message = message;

            _messages.Add(tm);
        }

        /// <summary>
        /// Stops this instance and records information to the <see cref="IFWFLog"/>.
        /// </summary>
        public void Close()
        {
            // Do not allow to close more than once
            if (_isClosed)
            {
                return;
            }

            _isClosed = true;

            //
            AddMessage(
                string.Format("{0} Complete", _name)
                );

            //
            if (_stopWatch.IsRunning)
            {
                _stopWatch.Stop();
            }

            // NOTE: Log all messages associated with the duration of the task
            if (_log.IsTraceEnabled)
            {
                foreach (TimedMessage tm in _messages)
                {
                    var logData = new LoggingEventData();
                    logData.Level = Level.Trace;

                    logData.Message = string.Format(
                        "{0}:{1} [{2}] {3}: {4}",
                        _category,
                        _name,
                        _instance,
                        tm.Elapsed,
                        tm.Message
                        );

                    _log.Log(logData);
                }
            }

            // NOTE: Create a special log entry with properties of the duration of the task
            if (_log.IsStatisticsEnabled)
            {
                var logData = _log.PrepareLogEvent( typeof( TimeTaken ) );
                logData.Level = ExtendedLogImplementation.Statistics;

                logData.Properties = new PropertiesDictionary();
                logData.Properties["stat.name"] = _name;
                logData.Properties["stat.category"] = _category;
                logData.Properties["stat.instance"] = _instance;
                logData.Properties["stat.duration"] = _stopWatch.Elapsed.ToString();

                _log.Log(logData);
            }

            // Clean up
            _messages.Clear();
        }

    }
}
